﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace DDD.Core {
    /// <summary>
    /// 頂点データを保持する抽象クラス
    /// </summary>
    /// <remarks>
    /// <see cref="VertexArray"/> クラスは <see cref="Attribute"/> 変数に送る頂点データを保存するためのコンテナ クラスです｡
    /// <img src="VertexArrayDiagram.png"/>
    /// 保存するデータの型に依存しない <see cref="VertexArray"/> クラスと､
    /// 型に依存する同名の派生クラス<see cref="VertexArray{T}"/> が存在します｡
    /// <c>Attribute</c> 変数の詳細については､ <see cref="VertexBuffer"/> の解説を参照してください。
    /// 頂点数は[1,<see cref="Int32.MaxValue"/>]の範囲が有効です｡コンポーネント数は[1,4]の範囲が有効です｡
    /// <note type="implement">
    /// 例外条件のチェックは派生クラスではなく､この基底クラスで行うべきだと思う｡
    /// 後で修正する｡
    /// </note>
    /// </remarks>
    /// <seealso cref="AttributeVariable"/>
    /// <seealso cref="VertexBuffer"/>
    /// <seealso cref="VertexAarray{T}"/>
    public abstract class VertexArray : Object {

        #region Field
        int vertexCount;
        int componentCount;
        ComponentType type;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// <paramref name="type"/> 型の頂点データを<paramref name="numVertives"/> x <paramref name="numComponents"/> 個
        /// 保存します｡
        /// </remarks>
        /// <param name="numVertices">頂点数</param>
        /// <param name="numComponents">1頂点あたりのコンポーネント数</param>
        /// <param name="type">保存するデータの型</param>
        /// <exception cref="ArgumentException"><paramref name="numVertices"/> &lt;= 0 または <paramref name="numVertices" &gt;= <see cref="Int32.MaxValue"/></exception>
        /// <exception cref="ArgumentException"><paramref name="numComponents"/> &lt; 1 または <paramref name="numComponents"/> &gt; 4 </exception>
        public VertexArray (int numVertices, int numComponents, Type type) {
            if (numVertices <= 0 || numVertices >= Int32.MaxValue) {
                throw new ArgumentException ("Number of vertices is invalid");
            }
            if (numComponents < 1 || numComponents > 4) {
                throw new ArgumentException ("Number of components is invalid");
            }
   
            this.vertexCount = numVertices;
            this.componentCount = numComponents;

            // この下激しくださいので何とかする

            if (type == typeof (byte)) {
                this.type = ComponentType.Byte;
            }
            if (type == typeof (sbyte)) {
                this.type = ComponentType.SByte;
            }
            if (type == typeof (short)) {
                this.type = ComponentType.Short;
            }
            if (type == typeof (ushort)) {
                this.type = ComponentType.UShort;
            }
            if (type == typeof (int)) {
                this.type = ComponentType.Int;
            }
            if (type == typeof (uint)) {
                this.type = ComponentType.UInt;
            }
            if (type == typeof (float)) {
                this.type = ComponentType.Float;
            }

        }
        #endregion

        #region Property
        /// <summary>
        /// 頂点数
        /// </summary>
        /// <remarks>
        /// この頂点データを構成する頂点の総数です。
        /// </remarks>
        public int VertexCount {
            get { return vertexCount; }
        }

        /// <summary>
        /// コンポーネント数
        /// </summary>
        /// <remarks>
        /// 1頂点あたりのコンポーネント数です｡
        /// </remarks>
        public int ComponentCount {
            get { return componentCount; }
        }

        /// <summary>
        /// 頂点データの型
        /// </summary>
        /// <remarks>
        /// 頂点のデータ型を <see cref="CompoentType"/> の中から返します｡
        /// </remarks>
        public ComponentType ComponentType {
            get { return type; }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }
        #endregion

        #region Method
        #endregion
    }

    /// <summary>
    /// 頂点データを保持するコンテナ クラス
    /// </summary>
    /// <remarks>
    /// 型に依存しない <see cref="VertexArray"/> クラスを継承して､型に依存する頂点データを保存するコンテナ クラスを実装します｡
    /// 保存できる頂点データの型(<typeparamref name="T"/>)は､以下のとおりです｡
    /// <list type="bullet">
    ///   <item><c>byte</c></item>
    ///   <item><c>short</c></item>
    ///   <item><c>ushort</c></item>
    ///   <item><c>int</c></item>
    ///   <item><c>uint</c></item>
    ///   <item><c>float</c></item>
    /// </list>
    /// <c>bool</c> 型は <c>Attribute</c> 変数として定義されていないので使用できません｡
    /// これ以外の型を指定してインスタンス化すると例外を発生します｡
    /// </remarks>
    /// <typeparam name="T">頂点データの型</typeparam>
    public class VertexArray<T> : VertexArray, IEnumerable<T> where T : struct {

        #region Field
        T[] buffer;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 指定の頂点数、コンポーネント数の頂点データを保存する <see cref="VertexArray{T}"/> オブジェクトを作成します｡
        /// <typeparamref name="T"/> に指定できる型は､以下の通りです｡
        /// <list type="bullet">
        ///   <item><c>byte</c></item>
        ///   <item><c>short</c></item>
        ///   <item><c>ushort</c></item>
        ///   <item><c>int</c></item>
        ///   <item><c>uint</c></item>
        ///   <item><c>float</c></item>
        /// </list>
        /// </remarks>
        /// <param name="numVertices">頂点数</param>
        /// <param name="numComponents">1頂点あたりのコンポーネント数 [1,4]</param>
        /// <exception cref="ArgumentException"><typeparamref name="T"/> が <c>byte, sbyte, short, ushort, int, uint, float</c> 以外の型のとき</exception>
        public VertexArray (int numVertices, int numComponents)
            : base (numVertices, numComponents, typeof (T)) {

            if (!(typeof (T) == typeof (byte) || typeof (T) == typeof (sbyte) || 
                typeof (T) == typeof (int) || typeof (T) == typeof (uint) ||
                typeof (T) == typeof (short) || typeof (T) == typeof (ushort) ||
                typeof (T) == typeof (float))) {
                throw new ArgumentException ("Type of VertexArray is invalid");
            }

            this.buffer = new T[numVertices * numComponents];
        }
        #endregion

        #region Property
        /// <inheritdoc/>
        public IEnumerable<T> Components {
            get { return buffer; }
        }
        #endregion

        #region Method

        /// <summary>
        /// すべての頂点データのクリア
        /// </summary>
        /// <remarks>
        /// 頂点データを全て規定の初期値(通常は0)で上書きします｡
        /// </remarks>
        public void Clear () {
            for (var i = 0; i < buffer.Length; i++) {
                this.buffer[i] = default (T);
            }
        }

        /// <inheritdoc/>
        IEnumerator<T> IEnumerable<T>.GetEnumerator () {
            return ((IEnumerable<T>)buffer).GetEnumerator ();
        }

        /// <inheritdoc/>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return buffer.GetEnumerator ();
        }

        /// <summary>
        /// 頂点データの取得
        /// </summary>
        /// <remarks>
        /// 保存している頂点データから値を取得します
        /// </remarks>
        /// <param name="firstVertex">取得を始める最初の頂点インデックス [0,<see cref="VertexCount"/>]</param>
        /// <param name="numVertices">取得する頂点数 [0,<see cref="VertexCount"/>]</param>
        /// <param name="dst">結果を書き込む <typeparamref name="T"/> 型の配列</param>
        /// <exception cref="ArgumentException"><paramref name="firstVertex"/> &lt; 0 または <paramref name="firstVertex"/> &gt;= <see cref="VertexCount"/></exception>
        /// <exception cref="ArgumentException"><paramref name="numVertices"/> &lt; 0 または <paramref name="firstVertex"/> + <paramref name="numVertices"/> &gt; <see cref="VertexCount"/></exception>
        /// <exception cref="ArgumentNullException"><paramref name="dst"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="dst"/>.<see cref="Array.Length"/> &lt; <paramref name="numVertices"/> * <see cref="ComponentCount"/></exception>
        public void Get (int firstVertex, int numVertices, T[] dst) {
            if (firstVertex < 0 || firstVertex >= VertexCount) {
                throw new ArgumentException ("First vertex is invalid");
            }
            if (numVertices < 0 || firstVertex + numVertices > VertexCount) {
                throw new ArgumentException ("Number of vertex is invalid");
            }
            if (dst == null) {
                throw new ArgumentNullException ("Dst is null");
            }
            if (dst.Length < numVertices * ComponentCount) {
                throw new ArgumentException ("Dst length is too short");
            }

            Array.Copy (buffer, firstVertex * ComponentCount, dst, 0, numVertices * ComponentCount);
        }

        /*
        /// <summary>
        /// 頂点データをfloatで取得します
        /// </summary>
        /// <remarks>
        /// 頂点データを内部の型にかかわらずfloatの値として取得します。
        /// データ型が<dic>byte</dic>,<dic>short</dic>,<dic>int</dic>の場合はその型で表現できる範囲を[-1,1]に正規化した値を返します。
        /// 同様にデータ型が<dic>ushort</dic>,<dic>uint</dic>の場合はその型で表現できる範囲を[0,1]に正規化した値を返します。
        /// <dic>float</dic>型の場合は常に加工無しにそのままの値を取得します。
        /// </remarks>
        /// <param name="firstVertex">取得を始める頂点インデックス</param>
        /// <param name="numVertices">頂点数</param>
        /// <param name="dst">結果を書き込むfloatの配列</param>
        public override void Get (int firstVertex, int numVertices, float[] dst) {
            if (firstVertex < 0 || firstVertex >= VertexCount) {
                throw new ArgumentException ("First vertex is invalid");
            }
            if (numVertices < 0 || firstVertex + numVertices > VertexCount) {
                throw new ArgumentException ("Number of vertex is invalid");
            }
            if (dst == null) {
                throw new ArgumentNullException ("Dst is null");
            }
            if (dst.Length < numVertices * ComponentCount) {
                throw new ArgumentException ("Dst length is too short");
            }

            for (int i = 0; i < numVertices * ComponentCount; i++) {
                switch (typeof (TValue).Name) {
                    case "Byte": dst[i] = Normalize ((float)(byte)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "SByte": dst[i] = Normalize ((float)(sbyte)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "Int16": dst[i] = Normalize ((float)(short)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "UInt16": dst[i] = Normalize ((float)(ushort)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "Int32": dst[i] = Normalize ((float)(int)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "UInt32": dst[i] = Normalize ((float)(uint)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "Single": dst[i] = (float)(ValueType)buffer[firstVertex * ComponentCount + i]; break;
                    default: throw new NotImplementedException("Sorry");
                }
            }
        }
        */

        /// <summary>
        /// 頂点データの書き込み
        /// </summary>
        /// <remarks>
        /// 頂点データを書き込みます｡頂点データはただちに内部に確保したバッファーにコピーされます｡
        /// </remarks>
        /// <param name="firstVertex">書き込みを始める最初の頂点インデックス [0,<see cref="VertexCount"/>]</param>
        /// <param name="numVertices">書き込む頂点数 [0,<see cref="VertexCount"/>]</param>
        /// <param name="src"><typeparamref name="T"/> 型の頂点データの配列</param>
        /// <exception cref="ArgumentException"><paramref name="firstVertex"/> &lt; 0 または <paramref name="firstVertex"/> &gt;= <see cref="VertexCount"/></exception>
        /// <exception cref="ArgumentException"><paramref name="numVertices"/> &lt; 0 または <paramref name="firstVertex"/> + <paramref name="numVertices"/> &gt; <see cref="VertexCount"/></exception>
        /// <exception cref="ArgumentNullException"><paramref name="src"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="src"/>.<see cref="Array.Length"/> &lt; <see cref="numVertices"/> * <see cref="ComponentCount"/></exception>
        public void Set (int firstVertex, int numVertices, T[] src) {
            if (firstVertex < 0 || firstVertex >= VertexCount) {
                throw new ArgumentException ("First vertex is invalid");
            }
            if (numVertices < 0 || firstVertex + numVertices > VertexCount) {
                throw new ArgumentException ("Number of vertex is invalid");
            }
            if (src == null) {
                throw new ArgumentNullException ("Src is null");
            }
            if (src.Length < numVertices * ComponentCount) {
                throw new ArgumentException ("Src length is too short");
            }

            Array.Copy (src, 0, this.buffer, firstVertex * ComponentCount, numVertices * ComponentCount);
        }



        /// <summary>
        /// <typeparamref name="T"/>型の[最小値,最大値]を[0,1]もしくは[-1,1]にマップします
        /// </summary>
        /// <remarks>
        /// ただしfloat型の場合は何もしません。
        /// </remarks>
        /// <note type="implement">
        /// ヘルパー関数に移動
        /// </note>
        /// <param name="t"><typeparamref name="T"/>型の値</param>
        /// <returns>正規化された値</returns>
        private float Normalize (float t) {
            var max = 0f;
            var min = 0f;
            switch (typeof (T).Name) {
                case "Byte": max = Byte.MaxValue; min = Byte.MinValue; break;
                case "SByte": max = SByte.MaxValue; min = SByte.MinValue; break;
                case "Int16": max = Int16.MaxValue; min = Int16.MinValue; break;
                case "UInt16": max = UInt16.MaxValue; min = UInt16.MinValue; break;
                case "Int32": max = Int32.MaxValue; min = Int32.MinValue; break;
                case "UInt32": max = UInt32.MaxValue; min = UInt32.MinValue; break;
                case "Single": max = 1; min = 0;break ;
                default: throw new NotImplementedException("Sorry");
            }
            // Unsigned型(0,1)
            if (min >= 0) {
                t = t / (max - min) - min / (max - min);
            }
            // Signed型(-1,1)
            if (min < 0) {
                t = t * 2 / (max - min) - (max + min) / (max - min);
            }
            return t;
        }

        /// <summary>
        /// [0,1]もしくは[-1,1]を<typeparamref name="T"/>型の[最小値,最大値]にマップします
        /// </summary>
        /// <remarks>
        /// ただしfloat型の場合は何もしません。
        /// </remarks>
        /// <note type="implement">
        /// ヘルパー関数に移動
        /// </note>
        /// <param name="t">正規化された値</param>
        /// <returns><typeparamref name="T"/>型で表せる[最小値,最大値]</returns>
        float Expand (float t) {
            var max = 0f;
            var min = 0f;
            switch (typeof (T).Name) {
                case "Byte": max = Byte.MaxValue; min = Byte.MinValue; break;
                case "SByte": max = SByte.MaxValue; min = SByte.MinValue; break;
                case "Int16": max = Int16.MaxValue; min = Int16.MinValue; break;
                case "UInt16": max = UInt16.MaxValue; min = UInt16.MinValue; break;
                case "Int32": max = Int32.MaxValue; min = Int32.MinValue; break;
                case "UInt32": max = UInt32.MaxValue; min = UInt32.MinValue; break;
                case "Single": max = 1; min = 0; break;
                default: throw new NotImplementedException("Sorry");
            }
            // Unsigned型(0,1)
            if (min >= 0) {
                t = t * (max - min) - min;
            }
            // Signed型(-1,1)
            if (min < 0) {
                t = t * (max - min) / 2.0f - (max + min) / 2.0f;
            }

            return t;
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("VertexArray: Vertices={0}, Components={1}, Type={2}", VertexCount, ComponentCount, ComponentType.ToString ());
        }
        #endregion

    }
}
